#pragma once
#include "sock.hpp"
#include <functional>

static const int defaultport = 8080;
static const int fdnum = sizeof(fd_set) * 8;
static const int defaultfd = -1;

using func_t = std::function<std::string(const std::string &)>;

class SelectServer
{
public:
    SelectServer(func_t f, int port = defaultport)
        : _port(port), _listensock(-1), _fdarray(nullptr), _func(f)
    {
    }

    void initServer()
    {
        _listensock = Sock::Socket();
        Sock::Bind(_listensock, _port);
        Sock::Listen(_listensock);
        // fd_set是文件描述符集，是一个位图结构，
        // 在使用select函数时，select根据这个位图来实现是否关心这个文件描述符的读，写以及异常事件
        // 所以sizeof(fd_set) * 8 （一个字节有8个比特位，所以这里要 * 8）就表示了这个位图结构能够容纳的最大的文件描述符的数量
        _fdarray = new int[fdnum];
        // 一开始将所有的位置改为-1，表示此时文件描述符不合法
        for (int i = 0; i < fdnum; i++)
            _fdarray[i] = defaultfd;
        // 将用于监听的fd放到数组中，并且在服务器退出之前，_listensock会一直存在
        _fdarray[0] = _listensock;
    }

    void Print()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < fdnum; i++)
        {
            if (_fdarray[i] != defaultfd)
                std::cout << _fdarray[i] << " ";
        }
        std::cout << std::endl;
    }

    // 用来获取新的连接
    void Accepter(int listensock)
    {
        logMessage(DEBUG, "Accepter in");
        // accetp函数 = 等待 + 获取连接
        // 到了这里，accept函数一定不会阻塞，因为等待的操作已经交给了select来完成
        std::string clientip;
        uint16_t clientport = 0;
        int sock = Sock::Accept(listensock, &clientip, &clientport);
        if (sock < 0)
            return;
        logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);
        // 获取到了sock(也就新的连接)，我们不能直接将sock交给recv、read等函数
        // 因为recv、read函数同样需要等待，而在整个代码中，只有select才有资格等待
        // 所以我们需要将新的sock交给select，也就是将sock添加到_fdarray数组中
        int i = 0;
        for (; i < fdnum; i++)
        {
            if (_fdarray[i] != defaultfd)
                continue;
            else
                break;
        }

        if (i == fdnum)
        {
            logMessage(WARNING, "server if full, please wait");
            close(sock);
        }
        else
        {
            _fdarray[i] = sock;
        }
        Print();
    }

    // 读取
    void Recver(int sock, int pos)
    {
        logMessage(DEBUG, "in Recver");

        char buffer[1024];
        // 这样的读取是有问题的，因为你无法保证你读取到的是一个完整的报文，需要定制协议
        // 在这里的读取同样不会被阻塞
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s] = 0;
            logMessage(NORMAL, "client# %s", buffer);
        }
        else if (s == 0)
        {
            close(sock);
            _fdarray[pos] = defaultfd;
            logMessage(NORMAL, "client quit");
            return;
        }
        else
        {
            close(sock);
            _fdarray[pos] = defaultfd;
            logMessage(ERROR, "client quit: %s", strerror(errno));
            return;
        }

        // 处理请求
        std::string response = _func(buffer);
        // 这里的写也有问题，正常来说应该还要一个写的文件描述符集，需要和读进行类似的操作
        write(sock, response.c_str(), response.size());
        logMessage(DEBUG, "out Recver");
    }

    void HandlerReadEvent(fd_set &rfds)
    {
        for (int i = 0; i < fdnum; i++)
        {
            if (_fdarray[i] == defaultfd)
                continue; // 过滤掉非法的fd
            // 如果是合法的，再检查文件描述符集中的文件描述符是否就绪，如果是则执行对应的操作
            if (FD_ISSET(_fdarray[i], &rfds) && _fdarray[i] == _listensock)
                Accepter(_listensock);
            else if (FD_ISSET(_fdarray[i], &rfds))
                Recver(_fdarray[i], i);
            else
            {
            }
        }
    }

    void start()
    {
        while (true)
        {
            fd_set rfds;    // 创建一个读文件描述符集对象
            FD_ZERO(&rfds); // 清除位图中的所有位
            int maxfd = _fdarray[0];

            for (int i = 0; i < fdnum; i++)
            {
                if (_fdarray[i] == defaultfd)
                    continue;               // 过滤掉不合法的文件描述符
                FD_SET(_fdarray[i], &rfds); // 将合法的文件描述符添加到读文件描述符集中

                if (maxfd < _fdarray[i])
                    maxfd = _fdarray[i]; // 找到最大的文件描述符
            }
            logMessage(NORMAL, "max fd is: %d", maxfd);

            // 表示只关心读事件，若想关心写和异常事件，则还需要设置对应的文件描述符集
            // 文件描述符集是一个位图结构，maxfd+1其实就是告诉系统需要检查的文件描述符范围。
            // rfds是一个输入输出型的参数，我们设置好了希望select需要关心的文件描述符，当对应的文件描述符没有就绪，select就会清除掉对应的位置
            // 所以在select之后，rfds中的文件描述符一定是已经就绪了的
            int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr);
            switch (n) // n表示有n个事件就绪
            {
            case 0:
                logMessage(NORMAL, "timeout...");
                break;
            case -1:
                logMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
                break;
            default:
                logMessage(NORMAL, "have event ready!");
                HandlerReadEvent(rfds); // 开始处理事件
                break;
            }
        }
    }

    ~SelectServer()
    {
        if (_listensock < 0)
            close(_listensock);
        if (_fdarray)
            delete[] _fdarray;
    }

private:
    int _port;
    int _listensock;
    int *_fdarray; // 用来管理获取的文件描述符数组
    func_t _func;
};